package org.wattdepot.simulation.oahugrid.strategy;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import org.wattdepot.simulation.oahugrid.demandcurve.DailyDemandCurve;
import org.wattdepot.simulation.oahugrid.demandcurve.DailyDemandCurve.PeakType;
import org.wattdepot.simulation.oahugrid.powerplant.PowerPlant;
import org.wattdepot.simulation.oahugrid.powerplant.PowerPlantSet;
import static org.wattdepot.simulation.oahugrid.demandcurve.DailyDemandCurve.PeakType.*;
import static org.wattdepot.simulation.oahugrid.powerplant.PowerPlant.Type.*;

/**
 * Generates simulation data by ordering the plants to request power according to peak type.
 * When offpeak, the order is baseload, cycling, peaking.  When shoulderpeak, the order is
 * cycling, baseload, peak.  When prioritypeak, the order is peaking, cycling, baseload.
 * @author Philip Johnson
 *
 */
public class ByPeakTypeStrategy implements SimulationStrategy {
  
  /**
   * Returns a list of all PowerPlants in order of how their energy should be used.
   * @param peakType The type of peak.
   * @param plantSet The set of plants. 
   * @return An ordered list.
   */
  private List<PowerPlant> getOrderedPlants(PeakType peakType, PowerPlantSet plantSet) {
    List<PowerPlant> plants = new ArrayList<PowerPlant>();
      if (peakType == OFFPEAK) {
        // fulfill power requirements from baseline, then cycling, then peak.
        plants.addAll(plantSet.getPlants(BASELOAD));
        plants.addAll(plantSet.getPlants(CYCLING));
        plants.addAll(plantSet.getPlants(PEAKING));
        plants.addAll(plantSet.getPlants(SPINNINGRESERVE));
      }
      else if (peakType == SHOULDERPEAK) {
        // fulfill power requirements from cycling, then baseline, then peak.
        plants.addAll(plantSet.getPlants(CYCLING));
        plants.addAll(plantSet.getPlants(BASELOAD));
        plants.addAll(plantSet.getPlants(PEAKING));
        plants.addAll(plantSet.getPlants(SPINNINGRESERVE));
      }
      else {
        // fulfill power requirements from peak, then cycling, then baseload
        plants.addAll(plantSet.getPlants(PEAKING));
        plants.addAll(plantSet.getPlants(CYCLING));
        plants.addAll(plantSet.getPlants(BASELOAD));
        plants.addAll(plantSet.getPlants(SPINNINGRESERVE));
      }
      if (plants.size() != PowerPlant.values().length) {
        throw new RuntimeException("Ordered plants does not include all plants.");
      }
      return plants;
  }

  /**
   * Implements the ByPeakType strategy for generating one day's data.
   * @param curve The daily demand curve.
   * @param plantSet The set of plants used for generating power. 
   * @return A list of SimulationEntry instances indicating the power output by each of the plants.
   */
  @Override
  public List<SimulationEntry> generateDay(DailyDemandCurve curve, PowerPlantSet plantSet) {
    List<SimulationEntry> dayEntries = new ArrayList<SimulationEntry>();
    
    //Throw a RuntimeException if the demand will exceed the supply.
    if (curve.getMaxMW() > plantSet.getCapacity()) {
      throw new RuntimeException(String.format("Demand of %d MW > supply %d MW.", 
          curve.getMaxMW(), plantSet.getCapacity()));
    }
    
    // Loop for each time interval in day and generate entries for each plant.
    for (int i = 0; i < 96; i++) {
      dayEntries.addAll(generateInterval(curve, plantSet, i));
    }
    return dayEntries;
  }

  /**
   * Implements the ByPeakType strategy for a single 15 minute interval during the day.
   * @param curve  The demand curve for this day.
   * @param plantSet The set of plants, ordered by their intended priority of usage. 
   * @param i The 15 minute interval of interest, ranging from 0 (midnight) to 95 (11:45pm).
   * @return The List of BatchSimulationEntries indicating the power produced by each 
   * plant during this interval of time. 
   */
  public List<SimulationEntry> generateInterval(
      DailyDemandCurve curve, PowerPlantSet plantSet, int i) {
    List<SimulationEntry> entries = new ArrayList<SimulationEntry>();
    Calendar timestamp = curve.getTimestamp(i);
    PeakType peakType = curve.getPeakType(i);
    int gridMW = curve.getGridMW(i);
    if (peakType == OFFPEAK) {
      for (PowerPlant plant : this.getOrderedPlants(peakType, plantSet)) {
        SimulationEntry entry = plant.getSimulationEntry(gridMW, timestamp);
        gridMW -= entry.getGridMW();
        entries.add(entry);
      }
    }
    else if (peakType == SHOULDERPEAK) {
      for (PowerPlant plant : this.getOrderedPlants(peakType, plantSet)) {
        // Cycling plants are only responsible for the current shoulder hump. 
        int requestMW;
        if (plant.getType() == CYCLING) {
          requestMW = gridMW - curve.getPeakMaxMW(OFFPEAK);
        }
        else {
          requestMW = gridMW;
        }
        SimulationEntry entry = plant.getSimulationEntry(requestMW, timestamp);
        gridMW -= entry.getGridMW();
        entries.add(entry);
      }
    }
    else {
      for (PowerPlant plant : this.getOrderedPlants(peakType, plantSet)) {
        // Cycling plants only need to satisfy the shoulderHump; peakers for the priorityHump;
        int requestMW = 0;
        if (plant.getType() == CYCLING) {
          requestMW = gridMW - curve.getPeakMaxMW(OFFPEAK);
        }
        else if (plant.getType() == PEAKING) {
          requestMW = gridMW - curve.getPeakMaxMW(SHOULDERPEAK);
        }
        else {
          requestMW = gridMW;
        }
        SimulationEntry entry = plant.getSimulationEntry(requestMW, timestamp);
        gridMW -= entry.getGridMW();
        entries.add(entry);
      }
    }
    return entries;
  }

}
